// ClientApp.xaml.cs
//
// Copyright (c) 2007 Mike Strobel
//
// This source code is subject to the terms of the Microsoft Reciprocal License (Ms-RL).
// For details, see <http://www.opensource.org/licenses/ms-rl.html>.
//
// All other rights reserved.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Windows;
using System.Windows.Media.Animation;
using System.Windows.Threading;
using Avalon.Windows.Controls;
using Avalon.Windows.Utility;

using log4net;
using log4net.Core;

using Microsoft.Practices.Composite.Logging;
using Microsoft.Practices.Composite.Modularity;
using Microsoft.Practices.Composite.Presentation.Regions;
using Microsoft.Practices.Composite.UnityExtensions;
using Microsoft.Practices.ServiceLocation;
using Microsoft.Practices.Unity;
using Microsoft.Win32;

using Supremacy.Annotations;
using Supremacy.Client.Commands;
using Supremacy.Client.Context;
using Supremacy.Client.Logging;
using Supremacy.Client.Services;
using Supremacy.Resources;
using Supremacy.Threading;
using Supremacy.Utility;
using Supremacy.VFS;

using Xceed.Wpf.DataGrid;

using Logger = Supremacy.Utility.Logger;
using Scheduler = System.Concurrency.Scheduler;

using Supremacy.Collections;

namespace Supremacy.Client
{
    /// <summary>
    /// Interaction logic for MyApp.xaml
    /// </summary>
    public partial class ClientApp : IClientApplication
    {
        #region Constants
        public static readonly ClientCommandLineArguments CmdLineArgs;
        #endregion

        #region Fields
        private static readonly DispatcherOperationCallback ExitFrameCallback = ExitFrame;

        private static SplashScreen _splashScreen;
        private static Mutex _singleInstanceMutex;
        
        private bool _isShuttingDown;
        #endregion

        #region Constructors
        static ClientApp()
        {
            CmdLineArgs = new ClientCommandLineArguments();
            VfsWebRequestFactory.EnsureRegistered();
        }
        #endregion

        #region Properties and Indexers
        public new static ClientApp Current
        {
            get { return (ClientApp)Application.Current; }
        }

        public static Version ClientVersion
        {
            get { return Current.Version; }
        }

        public Version Version
        {
            get { return Assembly.GetEntryAssembly().GetName().Version; }
        }

        public bool IsShuttingDown
        {
            get { return _isShuttingDown; }
        }

        public IClientCommandLineArguments CommandLineArguments
        {
            get { return CmdLineArgs; }
        }
        #endregion

        #region Methods
        /// <summary>
        /// Processes all UI messages currently in the message queue.
        /// </summary>
        public static void DoEvents()
        {
            // Create new nested message pump.
            var nestedFrame = new DispatcherFrame();

            // Dispatch a callback to the current message queue, when getting called, 
            // this callback will end the nested message loop.
            // The priority of this callback should be lower than the that of UI event messages.
            DispatcherOperation exitOperation = Dispatcher.CurrentDispatcher.BeginInvoke(
                DispatcherPriority.Background,
                ExitFrameCallback,
                nestedFrame);

            // Pump the nested message loop.  The nested message loop will 
            // immediately process the messages left inside the message queue.
            Dispatcher.PushFrame(nestedFrame);

            // If the "ExitFrame" callback doesn't get finished, Abort it.
            if (exitOperation.Status != DispatcherOperationStatus.Completed)
            {
                exitOperation.Abort();
            }
        }

        public static void ReloadResources()
        {
            ReloadResources(true);
        }

        public static void ReloadResources(bool loadEmpireTheme)
        {
            if (Current.IsShuttingDown)
                return;
            IClientContext clientContext = null;
            if (loadEmpireTheme)
            {
                try
                {
                    if (ServiceLocator.Current != null)
                        clientContext = ServiceLocator.Current.GetInstance<IClientContext>();
                }
                catch {}
            }

            try
            {
                Current.Resources = LoadComponent(
                                    new Uri(
                                        "/SupremacyClient;Component/themes/Default.xaml",
                                        UriKind.RelativeOrAbsolute))
                                as ResourceDictionary;
            }
            catch {}

            ResourceDictionary themeDictionary = null;

            if (loadEmpireTheme && (clientContext != null))
            {
                if (!clientContext.IsGameInPlay)
                    return;

                var themeUri = new Uri(
                    String.Format(
                        "/SupremacyClient;Component/themes/{0}/Theme.xaml",
                        clientContext.LocalPlayer.Empire.Key),
                    UriKind.RelativeOrAbsolute);

                try
                {
                    themeDictionary = LoadComponent(themeUri) as ResourceDictionary;
                }
                catch {}
            }

            if (themeDictionary == null)
            {
                try
                {
                    var themeUri = new Uri(
                        "/SupremacyClient;Component/themes/Generic/Theme.xaml",
                        UriKind.RelativeOrAbsolute);
                    themeDictionary = LoadComponent(themeUri) as ResourceDictionary;
                }
                catch {}
            }

            if ((themeDictionary != null) && (Current.Resources != null))
            {
               Current.Resources.MergedDictionaries.Add(themeDictionary);
            }
        }

        protected override void OnExit(ExitEventArgs e)
        {
            _isShuttingDown = true;
            if (!CmdLineArgs.AllowMultipleInstances)
            {
                _singleInstanceMutex.ReleaseMutex();
            }
        }

        protected override void OnStartup(StartupEventArgs e)
        {
            Debug.Assert(Scheduler.Dispatcher.Dispatcher == this.Dispatcher);

            Licenser.LicenseKey = "DGF20-AUTJ7-3K8MD-DNNA";

            Timeline.DesiredFrameRateProperty.OverrideMetadata(
               typeof(Timeline),
               new FrameworkPropertyMetadata(ClientSettings.Current.DesiredAnimationFrameRate));

            base.OnStartup(e);

            var bootstrapper = new Bootstrapper();
            bootstrapper.Run();
        }

        [MethodImpl(MethodImplOptions.Synchronized)]
        internal static void HandleError(Exception e)
        {
            if (e is AppDomainUnloadedException)
                return;

            lock (Current)
            {
                Exception ie = e;

                while (ie != null)
                {
                    Console.Error.WriteLine(ie.Message);
                    Console.Error.WriteLine();
                    Console.Error.WriteLine(ie.StackTrace);
                    Console.Error.WriteLine();
                    Console.Error.WriteLine("----------------------------------------");
                    Console.Error.WriteLine();
                    Console.Error.Flush();
                    ie = ie.InnerException;
                }

                MessageBox.Show(
                    "An unhandled exception has occurred.  Detailed error information is "
                    + "available in the 'Error.txt' file.",
                    "Unhandled Exception",
                    MessageBoxButton.OK,
                    MessageBoxImage.Error);

                Environment.Exit(Environment.ExitCode);
            }
        }

        private static void Current_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            e.Handled = true;
            ServiceLocator.Current.GetInstance<IUnhandledExceptionHandler>().HandleError(e.Exception);
        }

        private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            ServiceLocator.Current.GetInstance<IUnhandledExceptionHandler>().HandleError((Exception)e.ExceptionObject);
        }

        private static Object ExitFrame(Object state)
        {
            // Exit the nested message loop.
            var frame = state as DispatcherFrame;
            if (frame != null)
            {
                frame.Continue = false;
            }
            return null;
        }
        #endregion

        [UsedImplicitly]
        private static class EntryPoint
        {
            [STAThread, UsedImplicitly]
            private static void Main(string[] args)
            {
                //if (!CheckNetFxVersion())
                //    return;

                /*var scheduler = new InOrderScheduler();

                var subject = new Subject<int>();
                subject
                    .ObserveOn(scheduler)
                    .Do(Console.WriteLine)
                    .Subscribe();

                for (int i = 0; i < 5; i++)
                    subject.OnNext(i + 1);

                Thread.Sleep(TimeSpan.FromSeconds(10));

                for (int i = 5; i < 10; i++)
                    subject.OnNext(i + 1);

                Thread.Yield();

                subject.OnNext(-1);
    */
                //var db = ResourceManager.Database;
                //var strings = db.ObjectStrings.OfType<ObjectString>().Where(o => o.Culture == "en").ToList();

                //var entries = strings.Select(
                //    o =>
                //    new XElement(
                //        "Entry",
                //        new XAttribute(
                //            "Key",
                //            o.Key),
                //        new XElement(
                //            "LocalizedEntries",
                //            new XElement(
                //                "LocalizedEntry",
                //                new XAttribute(
                //                    "Language",
                //                    o.Culture),
                //                new XElement("Name", new XCData(o.Name)),
                //                new XElement("Description", new XCData(o.Description)),
                //                new XElement("Custom1", new XCData(o.Custom1)),
                //                new XElement("Custom2", new XCData(o.Custom2))))));

                //var xml =
                //    new XElement(
                //        "TextDatabase",
                //        new XElement(
                //            "Tables",
                //            new XElement(
                //                "Table",
                //                new XAttribute(
                //                    "EntryType",
                //                    typeof(ITechObjectTextDatabaseEntry).FullName),
                //                new XElement(
                //                    "Entries",
                //                    entries))));

                try
                {
                    ShowSplashScreen();
                    StartClient(args);
                }
                catch (Exception e)
                {
                    while (e.InnerException != null)
                        e = e.InnerException;
                    throw e;
                }
            }

        }

        private static bool CheckNetFxVersion()
        {
            try
            {
                var netFxVersionKey = Registry.GetValue(
                    @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5",
                    "Version",
                    null);
                if (netFxVersionKey != null)
                {
                    var netFxVersion = new Version(netFxVersionKey.ToString());
                    if (netFxVersion < new Version(3, 0, 0, 0))
                    {
                        MessageBox.Show(
                            "Star Trek: Supremacy requires the Microsoft .NET Framework v3.5 SP1."
                            + Environment.NewLine
                            + "It must be installed before running the game.",
                            "Star Trek: Supremacy",
                            MessageBoxButton.OK,
                            MessageBoxImage.Hand);
                        return false;
                    }
                    if (netFxVersion < new Version("3.5.30729.01"))
                    {
                        ShowNetFxVersionMissingDialog();
                        return false;
                    }
                }
            }
            catch {}

            return true;
        }

        private static void StartClient(string[] args)
        {
            Logger.Initialize();
            GameLogManager.Initialize();

            ClientCommandLineArguments.Parse(CmdLineArgs, args);

            if (CmdLineArgs.Debug)
                Debugger.Break();

            /* If an instance of the game is already running, then exit. */
            if (!CmdLineArgs.AllowMultipleInstances)
            {
                bool mutexIsNew;
                _singleInstanceMutex = new Mutex(true, "{CC4FD558-0934-451d-A387-738B5DB5619C}", out mutexIsNew);
                if (!mutexIsNew)
                {
                    MessageBox.Show("An instance of Supremacy is already running.");
                    Environment.Exit(Environment.ExitCode);
                }
            }

            if (!string.IsNullOrWhiteSpace(CmdLineArgs.LogLevel))
            {
                var logLevel = LogLevelConverter.Instance.ConvertFromInvariantString(CmdLineArgs.LogLevel) as Level;
                if (logLevel != null)
                {
                    LogManager
                        .GetAllRepositories()
                        .OfType<log4net.Repository.Hierarchy.Hierarchy>()
                        .Select(o => o.Root)
                        .ForEach(
                            o =>
                            {
                                if (logLevel < o.Level)
                                    o.Level = logLevel;
                            });
                }
            }

            if (CmdLineArgs.TraceLevel != PresentationTraceLevel.None)
                PresentationTraceSources.Refresh();

            AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

            try
            {
                var debugWriter = File.CreateText("Trace.txt");
                var debugListener = new TextWriterTraceListener(debugWriter)
                {
#if DEBUG
                    Filter = new EventTypeFilter(SourceLevels.All),
#else
                    Filter = new EventTypeFilter(SourceLevels.Warning),
#endif
                    TraceOutputOptions = TraceOptions.Timestamp
                };
                Trace.AutoFlush = true;
                Trace.Listeners.Add(debugListener);
            }
            catch { }

            try
            {
                var errorFile = File.Open("Error.txt", FileMode.Create, FileAccess.Write, FileShare.ReadWrite);
                Console.SetError(new StreamWriter(errorFile));
            }
            catch
            {
                MessageBox.Show(
                    "The error log could not be created.  You may still run the game,\n"
                    + "but error details cannot be logged.",
                    "Warning",
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
            }

            var app = new ClientApp();
            app.DispatcherUnhandledException += Current_DispatcherUnhandledException;
            app.InitializeComponent();

            ReloadResources(false);

            //var gameWindow = new GameWindow();
            //gameWindow.SourceInitialized += OnGameWindowSourceInitialized;

            app.Run();
        }

        private static void ShowSplashScreen()
        {
            _splashScreen = new SplashScreen("resources/images/backgrounds/splash.png");
            _splashScreen.Show(false);
        }

        private static void ShowNetFxVersionMissingDialog()
        {
            var taskDialog =
                new TaskDialog
                {
                    AllowDialogCancellation = false,
                    Content = "Star Trek: Supremacy requires the Microsoft .NET Framework v3.5 SP1."
                              + Environment.NewLine
                              + "It must be installed before running the game.",
                    MainIcon = TaskDialogIconConverter.ConvertFrom(TaskDialogIcon.Shield),
                    Header = ".NET Framework v3.5 SP1 Required",
                    Title = "Star Trek: Supremacy",
                    CommandLinks =
                        {
                            new TaskDialogButtonData(
                                1,
                                "Go to Download",
                                "Take me to the framework download page.",
                                true),
                            new TaskDialogButtonData(
                                0,
                                "Exit",
                                "Exit the game without downloading the framework.")
                        }
                };

            taskDialog.LayoutUpdated +=
                delegate
                {
                    var window = Window.GetWindow(taskDialog);
                    if (window == null)
                        return;
                    window.Left = (SystemParameters.WorkArea.Width - window.ActualWidth) / 2;
                    window.Top = (SystemParameters.WorkArea.Height - window.ActualHeight) / 2;
                };

            taskDialog.Show();

            var result = taskDialog.Result;
            if (result.ButtonData.Value == 1)
            {
                UIHelpers.LaunchBrowser(
                    new Uri(
                        "http://www.microsoft.com/downloads/details.aspx?FamilyId=AB99342F-5D1A-413D-8319-81DA479AB0D7"));
                DoEvents();
            }
        }

        private static void OnGameWindowSourceInitialized(object sender, EventArgs e)
        {
            ((Window)sender).SourceInitialized -= OnGameWindowSourceInitialized;
            _splashScreen.Close(TimeSpan.Zero);
        }

        #region Bootstrapper Class
        private class Bootstrapper : UnityBootstrapper
        {
            private ClientWindow _shell;
            private ILoggerFacade _clientLogger;

            protected override ILoggerFacade LoggerFacade
            {
                get
                {
                    if (_clientLogger == null)
                    {
                        TextWriter textWriter;
                        try { textWriter = new StreamWriter("ClientLog.txt", false); }
                        catch{ textWriter = Console.Out; }
                        _clientLogger = new FilteredTextLogger(textWriter);
                    }
                    return _clientLogger;
                }
            }

            protected override IModuleCatalog GetModuleCatalog()
            {
                return new ConfigurationModuleCatalog().AddModule(ClientModule.ModuleName, typeof(ClientModule).AssemblyQualifiedName);
            }

            protected override RegionAdapterMappings ConfigureRegionAdapterMappings()
            {
                var baseMappings = base.ConfigureRegionAdapterMappings();
                baseMappings.RegisterMapping(
                    typeof(GameScreenStack),
                    this.Container.Resolve<GameScreenStackRegionAdapter>());
                return baseMappings;
            }

            protected override void ConfigureContainer()
            {
                base.ConfigureContainer();
                this.Container.RegisterInstance<IApplicationSettingsService>(new ApplicationSettingsService(), new ContainerControlledLifetimeManager());
                this.Container.RegisterInstance<IClientApplication>(Current, new ContainerControlledLifetimeManager());
                this.Container.RegisterInstance<IDispatcherService>(new DefaultDispatcherService(Dispatcher.CurrentDispatcher), new ContainerControlledLifetimeManager());
                this.Container.RegisterType<IUnhandledExceptionHandler, ClientUnhandledExceptionHandler>(new ContainerControlledLifetimeManager());
                this.Container.RegisterType<INavigationCommandsProxy, NavigationCommandsProxy>(new ContainerControlledLifetimeManager());
                this.Container.RegisterInstance<IClientContext>(new ClientContext(), new ContainerControlledLifetimeManager());
                this.Container.RegisterType<IResourceManager, ClientResourceManager>(new ContainerControlledLifetimeManager());
                this.Container.RegisterType<IGameErrorService, GameErrorService>(new ContainerControlledLifetimeManager());
                this.Container.RegisterType<IAudioEngine, AudioEngine>(new ContainerControlledLifetimeManager());
                this.Container.RegisterInstance(ResourceManager.VfsService, new ContainerControlledLifetimeManager());
            }

            protected override DependencyObject CreateShell()
            {
                if (_shell != null)
                    return _shell;
                _shell = this.Container.Resolve<ClientWindow>();
                _shell.SourceInitialized += OnGameWindowSourceInitialized;
                _shell.Show();
                //ShellIntegration.TaskListManager.PopulateTaskList(
                //    new Uri(Assembly.GetEntryAssembly().CodeBase, UriKind.Absolute).LocalPath,
                //    new WindowInteropHelper(_shell).Handle);
                this.Container.RegisterInstance<IGameWindow>(_shell, new ContainerControlledLifetimeManager());
                return _shell;
            }
        }
        #endregion
    }
}